﻿using System;
using System.Collections.Generic;
using System.IO;

using Ikadn.Ikon.Types;
using Ikadn.Utilities;

namespace Ikadn.Ikon.Factories
{
	/// <summary>
	/// IKADN object factory for IKON  textual objects from backslash escaped
	/// format.
	/// </summary>
	public class TextFactory : IIkadnObjectFactory
	{
		/// <summary>
		/// Sign for IKADN textual object (backslash escaped format).
		/// </summary>
		public const char OpeningSign = '"';

		/// <summary>
		/// Closing character for IKON textual object in textual
		/// representation (backslash escaped format).
		/// </summary>
		public const char ClosingChar = '"';

		private const char EscapeChar = '\\';
		private const char Unicode16Char = 'u';
		private const char Unicode32Char = 'U';
		
		static Dictionary<char, char> EscapeCodes = DefineEscapeCodes();

		/// <summary>
		/// Sign for IKADN textual object (backslash escaped format).
		/// </summary>
		public char Sign
		{
			get { return OpeningSign; }
		}

		/// <summary>
		/// Parses input for a IKADN object.
		/// </summary>
		/// <param name="parser">IKADN parser instance.</param>
		/// <returns>IKADN value generated by factory.</returns>
		public IkadnBaseObject Parse(Ikadn.IkadnParser parser)
		{
			if (parser == null)
				throw new System.ArgumentNullException("parser");

			bool escaping = false;
			int unicodeDigits = 0;
			long unicodeChar = 0;
			string text = parser.Reader.ReadConditionally(nextChar =>
			{
				char c = (char)nextChar;

				if (unicodeDigits > 0) {
					unicodeChar *= 16;
					try {
						unicodeChar += Convert.ToInt32(c.ToString(), 16);
					}
					catch (FormatException) {
						throw new FormatException("Unexpected character after " + parser.Reader.PositionDescription + " while reading unicode character number for IKON textual data.");
					}
					unicodeDigits--;
					
					ReadingDecision decision;
					if (unicodeDigits % 4 == 0) {
						decision = new ReadingDecision((char)unicodeChar, CharacterAction.Substitute);
						unicodeChar = 0;
					}
					else
						decision = new ReadingDecision(c, CharacterAction.Skip);

					escaping = unicodeDigits > 0;
					return decision;
				}

				if (escaping) {
					if (EscapeCodes.ContainsKey(c)) {
						escaping = false;
						return new ReadingDecision(EscapeCodes[c], CharacterAction.Substitute);
					}
					else if (c == Unicode16Char) {
						unicodeDigits = 4;
						unicodeChar = 0;
						return new ReadingDecision(c, CharacterAction.Skip);
					}
					else if (c == Unicode32Char) {
						unicodeDigits = 8;
						unicodeChar = 0;
						return new ReadingDecision(c, CharacterAction.Skip);
					}
					else
						throw new FormatException("Unsupported string escape sequence: \\" + nextChar);
					
				}
				switch (nextChar) {
					case EscapeChar:
						escaping = true;
						return new ReadingDecision(c, CharacterAction.Skip);
					case ClosingChar:
						return new ReadingDecision(c, CharacterAction.Stop);
					default:
						return new ReadingDecision(c, CharacterAction.AcceptAsIs);
				}
			});

			if (parser.Reader.Peek() != ClosingChar)
				throw new EndOfStreamException("Unexpected end of stream at " + parser.Reader.PositionDescription + " while reading IKON textual data.");
			parser.Reader.Read();

			return new IkonText(text);
		}

		private static Dictionary<char, char> DefineEscapeCodes()
		{
			Dictionary<char, char> res = new Dictionary<char, char>();

			res.Add('\\', '\\');
			res.Add('"', '"');
			res.Add('n', '\n');
			res.Add('r', '\r');
			res.Add('t', '\t');

			return res;
		}
	}
}
